package org.zmhhxl.sample3.oauth2.a.authentications.model;

import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import org.zmhhxl.api.auth.domain.entity.Role;
import org.zmhhxl.api.auth.domain.entity.UserInfo;

import java.io.Serial;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonTypeName("CustomUserDetails")
@Getter
@Setter
public class CustomUserDetails /*extends User*/  implements UserDetails, CredentialsContainer {
   @Serial
   private static final long serialVersionUID = 1L;

   /**
    * 扩展字段：用户ID
    */
   private Long userId;

   /**
    * 扩展字段：部门ID
    */
   private Long deptId;

   /**
    * 用户角色数据权限集合
    */
   private Integer dataScope;
   private Set<String> permissions;  //permit

   /**
    * @see User
    * 默认字段
    */
   private String username;
   private String password;
   private Boolean enabled;
   private Collection<GrantedAuthority> authorities;
   private boolean accountNonExpired;
   private boolean accountNonLocked;
   private boolean credentialsNonExpired;

   /**
    * Calls the more complex constructor with all boolean arguments set to {@code true}.
    *
    * @param userInfo
    */
   public CustomUserDetails(UserInfo userInfo) {
      //this.setUserId(userInfo.getId());
      this.userId = userInfo.getId();
      this.username = userInfo.getUsername();
      this.enabled = userInfo.isEnabled();
      this.password = userInfo.getPassword();
      this.accountNonExpired = true;
      this.credentialsNonExpired = true;
      this.accountNonLocked = true;
      if (CollectionUtil.isNotEmpty(userInfo.getAuthorities())) {
         this.authorities = userInfo.getAuthorities().stream()
               .map(Role::getRoleName)
               .map(SimpleGrantedAuthority::new)
               .collect(Collectors.toSet());
      }
   }

      public CustomUserDetails(
         /*@JsonProperty(value = "userId",required = true)*/ Long userId,
         /*@JsonProperty(value = "username",required = true) */String username,
         /*@JsonProperty(value = "password",required = true)*/ String password,
        /*@JsonProperty(value = "enabled",required = true) */boolean enabled,
         Integer dataScope,
         Long deptId,
         /*@JsonProperty("accountNonExpired") */boolean accountNonExpired,
         /*@JsonProperty("credentialsNonExpired")*/ boolean credentialsNonExpired,
         /*@JsonProperty("accountNonLocked")*/ boolean accountNonLocked,
         /*@JsonProperty("authorities")*/ Collection<? extends GrantedAuthority> authorities
   ) {
      Assert.isTrue(username != null && !"".equals(username) && password != null,
            "Cannot pass null or empty values to constructor");
      this.userId = userId;
      this.username = username;
      this.password = password;
      this.enabled = enabled;
      this.dataScope = dataScope;
      this.deptId = deptId;
      this.accountNonExpired = accountNonExpired;
      this.credentialsNonExpired = credentialsNonExpired;
      this.accountNonLocked = accountNonLocked;
      this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

   @Override
   public Collection<GrantedAuthority> getAuthorities() {
      return this.authorities;
   }

   @Override
   public String getPassword() {
      return this.password;
   }

   @Override
   public String getUsername() {
      return this.username;
   }

   @Override
   public boolean isEnabled() {
      return this.enabled;
   }

   @Override
   public boolean isAccountNonExpired() {
      return this.accountNonExpired;
   }

   @Override
   public boolean isAccountNonLocked() {
      return this.accountNonLocked;
   }

   @Override
   public boolean isCredentialsNonExpired() {
      return this.credentialsNonExpired;
   }

   @Override
   public void eraseCredentials() {
      this.password = null;
   }

   private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
      Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
      // Ensure array iteration order is predictable (as per
      // UserDetails.getAuthorities() contract and SEC-717)
      SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new AuthorityComparator());
      for (GrantedAuthority grantedAuthority : authorities) {
         Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
         sortedAuthorities.add(grantedAuthority);
      }
      return sortedAuthorities;
   }

   /**
    * Returns {@code true} if the supplied object is a {@code User} instance with the
    * same {@code username} value.
    * <p>
    * In other words, the objects are equal if they have the same username, representing
    * the same principal.
    */
   @Override
   public boolean equals(Object obj) {
      if (obj instanceof CustomUserDetails user) {
         return (this.username.equals(user.getUsername()) && Objects.equals(this.userId, user.userId));
      }
      return false;
   }

   /**
    * Returns the hashcode of the {@code username}.
    */
   @Override
   public int hashCode() {
      return this.username.hashCode() + this.userId.hashCode();
   }

   @Override
   public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(getClass().getName()).append(" [");
      sb.append("userId=").append(this.userId).append(", ");
      sb.append("Username=").append(this.username).append(", ");
      sb.append("Password=[PROTECTED], ");
      sb.append("Enabled=").append(this.enabled).append(", ");
      sb.append("dataScope=").append(this.dataScope).append(", ");  //zmhhxl
      sb.append("AccountNonExpired=").append(this.accountNonExpired).append(", ");
      sb.append("credentialsNonExpired=").append(this.credentialsNonExpired).append(", ");
      sb.append("AccountNonLocked=").append(this.accountNonLocked).append(", ");
      sb.append("Granted Authorities=").append(this.authorities).append("]");
      return sb.toString();
   }

   private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {

      private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

      @Override
      public int compare(GrantedAuthority g1, GrantedAuthority g2) {
         // Neither should ever be null as each entry is checked before adding it to
         // the set. If the authority is null, it is a custom authority and should
         // precede others.
         if (g2.getAuthority() == null) {
            return -1;
         }
         if (g1.getAuthority() == null) {
            return 1;
         }
         return g1.getAuthority().compareTo(g2.getAuthority());
      }

   }
}
